#ifndef SHARK_H
#define SHARK_H

#define SHARK_CYCLIC_PATHS

#include <map>
#include <algorithm>
#include "BoneSystem.h"
#include "Texture.h"

using namespace std;

#define MaxFriendsVisible 5


//
// helper functions
void DrawSphere(const Vector3& pos, float radius);
void DrawSphere(const BoundingSphere& bs);
void DrawSphere_colored(const BoundingSphere& bs, float r=1,float g=0, float b=0);
void DrawBezierCurve(const BezierCurve& bc, bool bShowControlPoints=true);
void DrawBoundingBox(const glmath::BoundingBox& bb, float r=0, float g=1,float b=0);
void DrawRay(const glmath::Ray& ray, float length,float r=1,float g=1,float b=0);

/*
 *	its actually a Milkshape Ascii model for the sharks
 */
class FishModel
{
public:

	FishModel();
	~FishModel();

	struct Material
	{
		char	m_name[32];
		GLfloat m_ambient[4];
		GLfloat m_diffuse[4];
		GLfloat m_specular[4];
		GLfloat m_emissive[4];
		GLfloat	m_shininess;
		GLfloat m_transparency;
		//HTexture m_texture;
		GLuint m_texture;
		vector<int>	m_meshIndexes;		
	};

	struct Triangle
	{
		int		m_normalIndices[3];
		int		m_vertexIndices[3];
	};

	//	Vertex structure
	struct Vertex
	{
		Vector3 m_location;
		Vector2	m_texCoord;			//texture coords
	};

	struct Mesh
	{
		char	m_name[32];
		int		m_materialIndex;	// which material?
		vector<Vertex>	m_vertices;
		vector<Vector3>	m_normals;
		vector<Triangle> m_triangles; 
	};


	typedef vector<Mesh> MeshVector;
	typedef MeshVector::iterator MeshIter;


	MeshVector			m_meshes;
	vector<Material>	m_materials;	
	
	//
	// min and max values of the vertexes of the mesh
	float minX,minY,minZ;	
	float maxX,maxY,maxZ;
	int maxMeshVerts;		// the number of vertices the biggest mesh have


	bool LoadFromAsciiMilkshape(const char* filename);
	bool ReadMeshesMilkshapeAscii(FILE* pFile,int numMeshes);
	bool ReadMaterialsMilkshapeAscii(FILE* pFile,int numMaterials);
	void ApplyMaterial(int index);	
	void Clear();

	MeshIter MeshesBegin() {	return m_meshes.begin();	}
	MeshIter MeshesEnd() { return m_meshes.end();		}
};


class FishManager;




class Fish : public BoneSystem
{
public:

	Fish(FishManager* sm);
	~Fish();

	enum Type {
		Shark,
		Dolphin,
		Salmon
	};

	enum Action {
		Cruise,
		Chase,
		Evade,
		Mate
	};

	enum Gender {
		Male, Female
	};

	//
	// this represents a connection between a bone and a vertex
	// TODO should be in a class like cSkinnedModelWithBones or something
	class BoneVertexLink
	{
	public:
		BoneVertexLink(int boneIndex, int vertexIndex, float weight)
			:	m_boneIndex(boneIndex),
			m_vertexIndex(vertexIndex),
			m_weight(weight) {}
			int m_boneIndex;
			int m_vertexIndex;
			float m_weight;
	};


	typedef vector<BoneVertexLink>	LinkVector;
	typedef vector<LinkVector>		BoneLinks;	
	typedef map<int,LinkVector>		MeshBoneMap;	

protected:
	FishManager* mpContainer;
	MeshBoneMap* mpLinks;
	vector<float>* mpWeights;		// array of vectors of floats......

	int			mBoneCount;
	float		mLength;
	/** points to the model in the FishManager */
	string		mModelName;			
	bool		mbShowWireframe;
	int			mRootIndex;
	float		mBezierSpeed;
	float		mRouteFactor;
	/** for the static animation */
	double		mAngleFactor;
	Quaternion	mQuatTarget;
	/** current target to reach */
	Vector3		mTarget;			
	/** the path to follow till the next target....	*/
	BezierCurve mRoute;			
	/** the previous route (used for smooth transition) */
	BezierCurve mPrevRoute;	
	/** no previous route */
	bool		mbFirstTime;
	/** time passed since shark began to follow target */
	double		mTimeElapsed;
	/** the time that must be passed before the shark reaches target */
	double		mDestTime;			
	/** just a trick to make the shark favor the left paths (or the right if false) 
	so it actually make cycles. This will work if SHARK_CYCLIC_PATHS is defined */
	bool		mbLeftEyed;		

	
	BoundingSphere mBoundingSphere;
public:
	string GetModelName() const { return mModelName; }
	void SetModelName(string val) { mModelName = val; }
	BezierCurve GetRoute() const { return mRoute; }
	void SetRoute(BezierCurve val) { mRoute = val; }

protected:
	
	/** what is the fish currently doing (cruising, hunting...) */
	Action mCurrentAction;
	
public:
	Action GetCurrentAction() const { return mCurrentAction; }
	void SetCurrentAction(Action val) { mCurrentAction = val; }


protected:

	static Fish* mVisibleFriendsList[MaxFriendsVisible];

/* =============  fish attributes (profile) ================ */
protected:
	/** type of fish that can hunt this fish */
	vector<Type> mvPredators;
	/** type of fish that this fish can feast upon */
	vector<Type> mvPreys;		
	/** type of this fish */
	Type mType;		
	/** actual speed of the fish */
	float mSpeed;			
	/** maximum speed of the fish */
	float mMaxSpeed;
	/** min speed of the fish (roaming speed?) */
	float mMinSpeed;
	/** actual stamina of the fish */
	float mStamina;
	/** minimum stamina */
	float mMinStamina;
	/** maximum stamina */
	float mMaxStamina;
	/** actual hunger of the fish */
	float mHunger;			
	/** minimum hunger */
	float mMinHunger;
	/** maximum hunger */
	float mMaxHunger;
	/** hunger increase over time */
	float mHungerRate;		
	/** minimum distance required to catch the prey */
	float mFeedingDistance;		
	/** how much hunger reduction for the predator*/
	float mNutritionalValue;	
	/** Male/Female */
	Gender mGender;
	/** alive/dead state. Maybe this replaced with a life point system? */
	bool mIsAlive;
	/** agility determines how steep/sharp can turn */
	float mAgility;	
	/** rate this kind of fish can reproduce */
	float mReproductRate;
	/** fish current age (in seconds????)  */
	int	mAge;
	/** age stages (???) young, adult, old.... */
	int	mLifeSpan[3];
	
public:
	void AddToPredators(Type pred) {mvPredators.push_back(pred); } ;
	bool IsPredator(Type pred) { return ( std::find(mvPredators.begin(),mvPredators.end(),pred)!=mvPredators.end()); };
	/** add to the list of potential food
		normally there should be a check if this type is already in the predators list but nevermind........*/
	void AddToPreys(Type prey) { mvPreys.push_back(prey); }
	/** check if we can eat this type */
	bool IsPrey(Type prey) { return (std::find(mvPreys.begin(),mvPreys.end(),prey)!=mvPreys.end()); };
	Type GetType() const { return mType; }
	/** Set type of the fish
		Can be Shark, Dolphin, Salmon */
	void SetType(Type val) { mType = val; }
	float GetSpeed() const { return mSpeed; }
	void SetSpeed(float val) { mSpeed = val; }
	float GetMaxSpeed() const { return mMaxSpeed; }
	void SetMaxSpeed(float val) { mMaxSpeed = val; }
	float GetMinSpeed() const { return mMinSpeed; }
	void SetMinSpeed(float val) { mMinSpeed = val; }
	float GetStamina() const { return mStamina; }
	void SetStamina(float val) { mStamina = val; }
	float GetMinStamina() const { return mMinStamina; }
	void SetMinStamina(float val) { mMinStamina = val; }
	float GetMaxStamina() const { return mMaxStamina; }
	void SetMaxStamina(float val) { mMaxStamina = val; }
	float GetMinHunger() const { return mMinHunger; }
	void SetMinHunger(float val) { mMinHunger = val; }
	float GetMaxHunger() const { return mMaxHunger; }
	void SetMaxHunger(float val) { mMaxHunger = val; }
	float GetHungerRate() const { return mHungerRate; }
	void SetHungerRate(float val) { mHungerRate = val; }
	float GetFeedingDistance() const { return mFeedingDistance; }
	void SetFeedingDistance(float val) { mFeedingDistance = val; }
	float GetNutritionalValue() const { return mNutritionalValue; }
	void SetNutritionalValue(float val) { mNutritionalValue = val; }
	Gender GetGender() const { return mGender; }
	void SetGender(Gender val) { mGender = val; }
	bool GetIsAlive() const { return mIsAlive; }
	void SetIsAlive(bool val) { mIsAlive = val; }
	float GetAgility() const { return mAgility; }
	void SetAgility(float val) { mAgility = val; }
	//int GetLifeSpan() const { return mLifeSpan; }	//TODO correct this
	void SetLifeSpan(int young, int adult, int old) { mLifeSpan[0] = young; mLifeSpan[1]=adult; mLifeSpan[2]=old; }	
	float GetReproductRate() const { return mReproductRate; }
	void SetReproductRate(float val) { mReproductRate = val; }
	int GetAge() const { return mAge; }
	void SetAge(int val) { mAge = val; }
/* =============  end fish attributes (profile) ================ */


/* ========================= perception ================================ */
protected:

	
	float mSeeRadius;
	float mSmellRadius;
	float mHearRadius;
	
	
public:
	void SetSeeRadius(float sr) { mSeeRadius=sr;}
	float GetSeeRadius(){return mSeeRadius;	}
	float GetSmellRadius() const { return mSmellRadius; }
	void SetSmellRadius(float val) { mSmellRadius = val; }
	float GetHearRadius() const { return mHearRadius; }
	void SetHearRadius(float val) { mHearRadius = val; }

/* ======================== end of perception ========================== */



/* ========================  flocking ===================================*/
protected:
	
	
	/** personal preferred separation distance this fish wants to 
	maintain from others in his flock */
	float mPreferredSeparation;	
	
	/** personal minimum distance to stay away from any enemies or 
	predators */
	float mKeepawayDistance;	
	
	

public:
	
	float GetPreferredSeparation() const { return mPreferredSeparation; }
	void SetPreferredSeparation(float val) { mPreferredSeparation = val; }
	float GetKeepawayDistance() const { return mKeepawayDistance; }
	void SetKeepawayDistance(float val) { mKeepawayDistance = val; }
/* =======================  end flocking ==============================*/



public:

	
	//TODO add rest of accessors methods
// 	void SetType(Type type);
// 	Type GetType() const;
	BoundingSphere GetLocalBoundingSphere() const { return mBoundingSphere; }
	BoundingSphere GetWorldBoundingSphere() const;
	void Build(const FishModel* model, const char* modelname, int numSegments, 
		int rootIndex,	float* lengthPerSegment);
	void DrawWithGLElements(const FishModel* model);
	void Draw(const FishModel* model);
	void DrawShadow(const FishModel* model);
	void DrawBones();
	void Animate1(double rads);
	void Clear();

	//
	// motion stuff
	void FollowRoute(double dt);
	void SetRoute(const Vector3& dest);

	//
	// AI stuff
	void Think();
	void Act(double dt);
	float CanSee(Fish* fistptr);
	float CanSmell(Fish* fishptr);	//TODO
	float CanHear(Fish* fishptr);
	

	//
	// those functions take as argument the candidate new target and
	// try to modify it to avoid getting the shark out of the world bounds
	// or avoid collision between sharks
	void KeepTargetInsideWorld(Vector3 *newtarget) const;
	void AvoidOtherFishes(Vector3 *newtarget);	
	
	void SelectCruiseTarget();
	void RandomCruise(double dt);
	void EvadeEnemy();
	void ChasePrey();			// non functional yet...
	
};



	




//
// keep track of all the fishes
// Could also be called "Fish World" or something.....
class FishManager
{
public:

	typedef vector<Fish*> FishVector;
	typedef vector<Fish*>::iterator FishIter;
	typedef map<int,string> ModelNames;
	typedef map<string,FishModel*> ModelMap;
	typedef map<string,FishModel*>::iterator ModelIter;
	
	FishManager();
	~FishManager();

	void Init();
	void Clear();

	void DrawSharks();
	void Update(double dt);
	
 	BOOL*		mpbShowWireframe;
 	BOOL*		mpbShowSharkShadows;
 	BOOL*		mpbEnableCulling;
 	BOOL*		mpbShowBoundingSpheres;
 	BOOL*		mpbMultitexture;
 	BOOL*		mpbShowPaths;
 	GLfloat*	mpPlaneHeight;
 	GLfloat*	mpLightPos;	//points to the light position array in the CView
 	GLfloat*	mSharkShadowColor;

	Frustum*	mpFrustum;
	FishVector	mFishes;
	ModelMap	mModels;
	//ModelNames	mModelNames;

	

	void CheckForInitialCollisions();	
	int GetSharkCount() const { return (int)mFishes.size();	}
	Fish* GetFish(int index); 
	bool IsModelLoaded(const char* name)
	{	return !(mModels.find(name)==mModels.end());	}

	BoundingBox mWorldBounds;
	
};




#endif